package security;

import java.io.*;
import java.net.*;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Random;

import sd1.security.Digest;
import sd1.security.PublicKey;
import sd1.security.SymetricKey;

class CipherSocket extends Socket {
  
    
    private PublicKey otherSidePubKey;
  
    /* The InputStream used by the socket. */
    private InputStream in = null;
  
    /* The OutputStream used by the socket */
    private OutputStream out = null;
    
    private boolean client;
        
    private byte[] nouce;
    
    private SymetricKey symkey;
  
    /* 
     * Constructor for class CipherSocket. 
     */
    public CipherSocket() throws IOException {
        super();
        this.client = false;
        generateKeys();
    }
    
    /* 
     * Constructor for class CipherSocket. 
     */
    public CipherSocket(String host, int port) throws IOException {
        super(host, port);
        this.client = true;
        generateKeys();
    }
  
    /* 
     * Returns a stream of type CipherInputStream. 
     */
    public synchronized InputStream getInputStream() throws IOException {
        if (in == null) {
        	try {
				negotiateKeys();
			} catch (ClassNotFoundException e) {
				System.out.println("Falha ao negociar chaves!");
			} catch (Exception e) {
				System.out.println("Deu para aqui qualquercoisa");
			}
        }
        return in;
    }
  
    /* 
     *Returns a stream of type CipherOutputStream. 
     */
    public synchronized OutputStream getOutputStream() throws IOException {
        if (out == null) {
        	try {
				negotiateKeys();
			} catch (ClassNotFoundException e) {
				System.out.println("Falha ao negociar chaves!");
			} catch (Exception e) {
				System.out.println("Deu para aqui qualquercoisa");
				e.printStackTrace();
			}
        }
        return out;
    }
    
    private void negotiateKeys() throws Exception{
    	ObjectOutputStream oos = new ObjectOutputStream(super.getOutputStream());
    	ObjectInputStream ois = new ObjectInputStream(super.getInputStream());
    	if(client){//CLIENTE
    		
    		//Envia seu certificado
    		oos.writeObject(KeyManager.getInstance().getPublicCert());
    		oos.flush();
    		
    		//Recebe certificado do servidor
    		PublicCert otherSideCert = (PublicCert) ois.readObject();
    		Digest dig = null;
    		try {
    			dig = Digest.createDigest();
    			dig.update(otherSideCert.getName().getBytes());
    			dig.update(otherSideCert.getPublicKey());
    		} catch (NoSuchAlgorithmException e) {
    			System.out.println("Não conseguiu criar o digest!");
    		}
    		byte[] myCalcDigest = dig.getDigest();
    		if(!Arrays.equals(myCalcDigest,KeyManager.getInstance().getPublicCAKey().decrypt(otherSideCert.getDigest())))
    			System.out.println("Falha na autenticação do servidor!");
    		else{
    			
    		otherSidePubKey = PublicKey.createKey(otherSideCert.getPublicKey());
    		
    		//Envia [Nouce]Kservidor
    		oos.writeObject(otherSidePubKey.encrypt(nouce));
    		oos.flush();
    		
    		//Recebe [Nouce,NouceServidor,SK]Kcliente
    		byte [] newcomerNouce = KeyManager.getInstance().decript((byte[]) ois.readObject());
    		if(!Arrays.equals(nouce, newcomerNouce))
    			throw new Exception();
    		byte [] otherNouce = KeyManager.getInstance().decript((byte[]) ois.readObject());
    		byte [] symetricKeyBytes = KeyManager.getInstance().decript((byte[]) ois.readObject());
			symkey = SymetricKey.createKey(symetricKeyBytes);
			
			//Envia [NouceServidor]SK
			oos.writeObject(symkey.encrypt(otherNouce));
    		}
    	}
    	else{//SERVIDOR
    		
    		//Recebe certificado do servidor
    		PublicCert otherSideCert = (PublicCert) ois.readObject();
    		Digest dig = null;
    		try {
    			dig = Digest.createDigest();
    			dig.update(otherSideCert.getName().getBytes());
    			dig.update(otherSideCert.getPublicKey());
    		} catch (NoSuchAlgorithmException e) {
    			System.out.println("Não conseguiu criar o digest!");
    		}
    		byte[] myCalcDigest = dig.getDigest();
    		if(!Arrays.equals(myCalcDigest,KeyManager.getInstance().getPublicCAKey().decrypt(otherSideCert.getDigest()))){
    			System.out.println("Falha na autenticação do cliente!");
    			System.exit(0);
    		}
    		else{
    		//Envia seu certificado
    		oos.writeObject(KeyManager.getInstance().getPublicCert());
    		oos.flush();
    		
    		//Recebe [NouceCliente]Kcliente
    		byte [] otherNouce = KeyManager.getInstance().decript((byte[]) ois.readObject());
    		
    		otherSidePubKey = PublicKey.createKey(otherSideCert.getPublicKey());
    		
    		//Envia [NouceCliente,Nouce,SK]Kservidor
    		oos.writeObject(otherSidePubKey.encrypt(otherNouce));
    		oos.writeObject(otherSidePubKey.encrypt(nouce));
    		oos.writeObject(otherSidePubKey.encrypt(symkey.exportKey()));
    		oos.flush();
    		
    		//Recebe [Nouce]SK
    		byte [] newcomerNouce = symkey.decrypt((byte[]) ois.readObject());
    		if(!Arrays.equals(nouce, newcomerNouce))
    			throw new Exception();
    		}
    	}
    	in = new CipherInputStream(super.getInputStream(), symkey);
    	out = new CipherOutputStream(super.getOutputStream(), symkey);
    }
    
    private void generateKeys(){
    	Random r = new Random();
    	nouce = new byte[16];
    	r.nextBytes(nouce);
    	try {
			symkey = SymetricKey.createKey();
		} catch (Exception e) {
			e.printStackTrace();
		}
    }
}
